#include "types.h"
#include "param.h"
#include "defs.h"
#include "spinlock.h"

struct logheader {
  int n;
  int block[LOGSIZE];
};

struct log {
  struct spinlock lock;

  int dev;
  int start;
  int size;

  int outstanding;  //how many FS syscalls are executing.
  int committing;   // in commit(), please wait
  
  struct logheader lh;
} logger;

static void _log_print(void);

static void read_head(void){
  //read logger from dev + start
  struct buf * buf = bread(logger.dev, logger.start);
  struct logheader * header = (struct logheader*)(buf->data);
  //save disk-info to kernel memory (logger.lh)
  logger.lh.n = header->n;

#ifdef DEBUG
  _log_print();
#endif

  for(int i = 0 ; i < logger.lh.n; ++i){
    logger.lh.block[i] = header->block[i];
  }
  brelse(buf);
}
static void write_head(void) {
  struct buf * buf = bread(logger.dev, logger.start);
  struct logheader * header = (struct logheader*)(buf->data);

  header->n = logger.lh.n;
  for(int i = 0; i < header->n; ++i){
    header->block[i] = logger.lh.block[i];
  }
  bwrite(buf);
  brelse(buf);
}
//write blocks in log area into  block in device
// if recovering == 0 => unpin the block from log_write()
static void install_trans(int recovering) {
  for(int i = 0; i < logger.lh.n; ++i){
    struct buf * block_in_log = bread(logger.dev, logger.start + i + 1);
    struct buf * block_in_dev = bread(logger.dev, logger.lh.block[i]);
    memmove(block_in_dev->data, block_in_log->data, BSIZE);
    bwrite(block_in_dev);
    if(recovering == 0) // bpin(block_in_dev) for log_write 
      bunpin(block_in_dev);

    brelse(block_in_dev);
    brelse(block_in_log);
  }
}

static void recover_from_log(void){
  read_head();
  install_trans(1);
  logger.lh.n = 0;
  write_head();
}



void initlog(int dev, int start, int size){
  if(sizeof(struct logheader) > BSIZE)
    panic("logger index(logheader) size too big(>BSIZE)");

  initlock(&logger.lock, "logger");
  logger.dev = dev;
  logger.start = start;
  logger.size = size;
  recover_from_log();
}


//TODO: 
static int sleep(void * chan) {
  printf("sleeping...\n");
  return 0;
}
//TODO: 
static int wakeup(void * chan){
  printf("wakeup\n");
  return 0;
}

//syscall: 
//  begin_op();
//
//  bp = bread(..);
//  bp->data[..] = ....;
//  log_write(bp);
//  brelse(bp);
//
//  end_op();
void log_write(struct buf * b){
  acquire(&logger.lock);
  if(logger.lh.n >= LOGSIZE || logger.lh.n >= logger.size - 1){
    panic("log_write: too big transaction");
  }
  if(logger.outstanding < 1)  // in the logger.outstanding process
    panic("log_write outside of trans");

  int i = 0;
  for(; i < logger.lh.n; ++i){
    if(b->blockno == logger.lh.block[i]){
      // the writed block is in the logging blocks
      break;
    }
  }
  
  // the writed block is new to logger
  if(i == logger.lh.n){
    bpin(b); //add new block to logger
    logger.lh.block[i] = b->blockno; // new add or
    logger.lh.n ++;
  }
  release(&logger.lock);
}

static void commit();
// called at the start of each FS system call
void begin_op(void){

#define LOG_FULL(logger)   (((logger).lh.n + ((logger).outstanding + 1) * MAXOPBLOCKS) > LOGSIZE)

  acquire(&logger.lock);
  while(1) {
    if(logger.committing) {
      sleep(&logger);
    }
    else if(LOG_FULL(logger)){
      sleep(&logger);
    }
    else{
      logger.outstanding += 1;
      break;
    }
  }
  release(&logger.lock);
}

void end_op(void){
  
  int do_commit = 0;

  acquire(&logger.lock);
  logger.outstanding -= 1;

  if(logger.committing)
    panic("logger.commiting==1 when start of end_op.");

  if(logger.outstanding == 0){
    logger.committing = 1;
    do_commit = 1;
  }
  else {
    // begin_op may be waiting for logger space
    // wakeup process to finished the end_op for the syscall
    wakeup(&logger); 
  }
  release(&logger.lock);
  
  if(do_commit){
    commit();

    acquire(&logger.lock);

    logger.committing = 0;

    wakeup(&logger);
    release(&logger.lock);
  }
}

//write logger blocks from memory blocks
static void write_log(){
  //bprint();
  for(int i = 0; i < logger.lh.n; ++i){
    struct buf * block_in_dev = bread(logger.dev, logger.lh.block[i]);
    struct buf * block_in_log = bread(logger.dev, logger.start + i + 1);
    memmove(block_in_log->data, block_in_dev->data, BSIZE);
    bwrite(block_in_log);
    brelse(block_in_log);
    brelse(block_in_dev);
  }
  //bprint();
}

static void commit() {
  if(logger.lh.n > 0){
    write_log();
    write_head();

    install_trans(0); // unpin the log_write  operations
    
    //clear logger info 
    logger.lh.n = 0 ;
    write_head();
  }
}


static void _log_print() {
  printf("dev:[%d], start:[%d], size:[%d], outstanding:[%d], committing:[%d], header.n:[%d]\n", logger.dev, logger.start, logger.size, logger.outstanding, logger.committing, logger.lh.n);
}
void log_print(){
  acquire(&logger.lock);
  _log_print();
  release(&logger.lock);
}

int t__log_outstanding() {
  return logger.outstanding;
}

int t__log_num() {
  return logger.lh.n;
}
